package com.nulldev.util.web.URLUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.ClassCache;
import com.nulldev.util.data.Arrays.arrays.FastArrayList;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.manage.nullUtil;
import com.nulldev.util.web.URLUtils.internal.DataConnection;
import com.nulldev.util.web.URLUtils.internal.HTTPSUrlConnectionImpl;

import sun.net.www.protocol.http.HttpURLConnection;

/**
 * TODO: Should this class be marked for removal, future JRE versions don't
 * support this.
 */
public class nStreamHandlerFactory implements URLStreamHandlerFactory {
	public static class BlankInputStream extends InputStream {

		@Override
		public int read() throws IOException {
			return -1;
		}

		@Override
		public int read(byte[] b) throws IOException {
			return -1;
		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			return -1;
		}

		@Override
		public long skip(long n) throws IOException {
			return 0;
		}

		@Override
		public synchronized void reset() throws IOException {
		}

		@Override
		public int available() throws IOException {
			return 0;
		}

		@Override
		public boolean markSupported() {
			return false;
		}

		@Override
		public synchronized void mark(int readlimit) {
		}
	}

	public static final BlankInputStream BLANK = new BlankInputStream();

	public static class BlankStream extends URLConnection {

		protected BlankStream(URL var1) {
			super(var1);
		}

		@Override
		public void connect() throws IOException {
			this.connected = true;
		}

		@Override
		public InputStream getInputStream() throws IOException {
			return BLANK;
		}

	}

	public static class BlankStreamHTTP extends HttpURLConnection {

		public BlankStreamHTTP(URL u, Proxy p) throws IOException {
			super(u, p);
		}

		@Override
		public void connect() throws IOException {
			this.connected = true;
		}

		@Override
		public InputStream getInputStream() throws IOException {
			return BLANK;
		}

	}

	private static final Map<String, URLStreamHandler> registeredProtocols = FastMaps.map();
	public static final nStreamHandlerFactory INSTANCE = JVM.version() >= 16f ? null : new nStreamHandlerFactory();
	private static boolean init;

	/*
	 * 
	 * TODO: Add the following protocols: QCLP: qclp://__peer_id.qclp qclp://PEER_ID
	 * ANN: ann://127.0.0.1:4444/__ann00.ann ann://ANN Host/ANN Address AMBP:
	 * [IMPLEMENTED in 2.x] ambp://127.0.1.1:29292/ ambp://AMBP Listen Server/
	 * 
	 */

	static {
		if (!init && registeredProtocols.isEmpty()) {
			if (JVM.version() < 16f && !nullUtil.IS_ANDROID_BUILD)
				URL.setURLStreamHandlerFactory(INSTANCE);
			registerProtocol("data", new URLStreamHandler() {

				@Override
				protected URLConnection openConnection(URL u) throws IOException {
					return new DataConnection(u);
				}
			});
			registerProtocol("blank", new URLStreamHandler() {

				@Override
				protected URLConnection openConnection(URL u) throws IOException {
					return new BlankStream(u);
				}
			});
			init = true;
		}
	}

	public static boolean registerProtocol(final String protocol, final URLStreamHandler handler) {
		registeredProtocols.put(protocol, handler);
		return true;
	}

	@SuppressWarnings("static-access")
	@Override
	public URLStreamHandler createURLStreamHandler(String protocol) {
		if (this.registeredProtocols.containsKey(protocol)) {
			return this.registeredProtocols.get(protocol);
		}
		return getURLStreamHandler(protocol);
	}

	final static URLStreamHandlerFactory factory = null;
	static Hashtable<String, URLStreamHandler> handlers = new Hashtable<String, URLStreamHandler>();
	private static Object streamHandlerLock = new Object();
	private static final String protocolPathProp = "java.protocol.handler.pkgs";

	static URLStreamHandler getURLStreamHandler(String protocol) {
		URLStreamHandler handler = handlers.get(protocol);
		if (handler == null) {

			boolean checkedWithFactory = false;

			// Use the factory (if any)
			if (factory != null) {
				handler = factory.createURLStreamHandler(protocol);
				checkedWithFactory = true;
			}

			// Try java protocol handler
			if (handler == null) {
				String packagePrefixList = null;

				packagePrefixList = java.security.AccessController
						.doPrivileged((PrivilegedAction<String>) new sun.security.action.GetPropertyAction(protocolPathProp, ""));
				if (packagePrefixList != "") {
					packagePrefixList += "|";
				}

				// REMIND: decide whether to allow the "null" class prefix
				// or not.
				packagePrefixList += "sun.net.www.protocol";

				StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|");

				while (handler == null && packagePrefixIter.hasMoreTokens()) {

					String packagePrefix = packagePrefixIter.nextToken().trim();
					try {
						String clsName = packagePrefix + "." + protocol + ".Handler";
						Class<?> cls = null;
						try {
							cls = ClassCache.forName(clsName);
						} catch (ClassNotFoundException e) {
							ClassLoader cl = ClassLoader.getSystemClassLoader();
							if (cl != null) {
								cls = cl.loadClass(clsName);
							}
						}
						if (cls != null) {
							handler = (URLStreamHandler) cls.newInstance();
						}
					} catch (Exception e) {
						// any number of exceptions can get thrown here
					}
				}
			}

			synchronized (streamHandlerLock) {

				URLStreamHandler handler2 = null;

				// Check again with hashtable just in case another
				// thread created a handler since we last checked
				handler2 = handlers.get(protocol);

				if (handler2 != null) {
					return handler2;
				}

				// Check with factory if another thread set a
				// factory since our last check
				if (!checkedWithFactory && factory != null) {
					handler2 = factory.createURLStreamHandler(protocol);
				}

				if (handler2 != null) {
					// The handler from the factory must be given more
					// importance. Discard the default handler that
					// this thread created.
					handler = handler2;
				}

				// Insert this handler into the hashtable
				if (handler != null) {
					handlers.put(protocol, handler);
				}

			}
		}

		if (handler == null)
			return handler;
		else if (handler.getClass() == sun.net.www.protocol.http.Handler.class) {
			// JVMUtil.async_println("Filtered http stream");
			return new FilteredHTTPHandler();
		} else if (handler.getClass() == sun.net.www.protocol.https.Handler.class) {
			// JVMUtil.async_println("Filtered https stream");
			return new FilteredHTTPSHandler();
		}
		// JVMUtil.async_println("skipped. " + handler);
		return handler;

	}

	public static class FilteredHTTPHandler extends URLStreamHandler {

		@Override
		protected URLConnection openConnection(final URL u) throws IOException {
			if (!allowConnection(u, Proxy.NO_PROXY)) {
				return new BlankStreamHTTP(u, Proxy.NO_PROXY);
			} else
				return new HttpURLConnection(u, Proxy.NO_PROXY);
		}

		@Override
		protected URLConnection openConnection(final URL u, final Proxy proxy) throws IOException {
			if (!allowConnection(u, proxy)) {
				return new BlankStreamHTTP(u, proxy);
			} else
				return new HttpURLConnection(u, proxy);
		}

	}

	public static class FilteredHTTPSHandler extends URLStreamHandler {

		@Override
		protected URLConnection openConnection(URL u) throws IOException {
			if (!allowConnection(u, Proxy.NO_PROXY)) {
				return new BlankStreamHTTP(u, Proxy.NO_PROXY);
			}
			final sun.net.www.protocol.https.Handler h = new sun.net.www.protocol.https.Handler();
			return new HTTPSUrlConnectionImpl(u, Proxy.NO_PROXY, h);
		}

		@Override
		protected URLConnection openConnection(URL u, Proxy p) throws IOException {
			if (!allowConnection(u, p)) {
				return new BlankStreamHTTP(u, p);
			}
			final sun.net.www.protocol.https.Handler h = new sun.net.www.protocol.https.Handler();
			return new HTTPSUrlConnectionImpl(u, p, h);
		}

	}

	private static boolean allowConnection(final URL url, final Proxy proxy) {
		for (final nFilter filter : filters) {
			if (!filter.allowConnection(url, proxy)) {
				return false;
			}
		}
		return true;
	}

	private static final List<nFilter> filters = new FastArrayList<nFilter>(true);

	public static interface nFilter {
		// TODO: Move to nFilterAction. Actions Supported: allow(), redirectTo(url,
		// proxy), block()
		// TODO: Add a generic global filter with a blacklist and whitelist.
		public boolean allowConnection(final URL url, final Proxy proxy);
	}

	public static boolean addFilter(final nFilter filter) {
		return filters.add(filter);
	}

	public static void install() {
		// invokes static {}
	}
}
